/**
 * 
 */
package travibot.engine;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Set;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;

import travibot.connection.ConnectionFactory;
import travibot.core.LoginPageParser;
import travibot.core.Parser;
import travibot.core.ParserFactory;
import travibot.core.VillagesParser;
import travibot.core.tags.Input;
import travibot.core.tags.LoginForm;
import travibot.engine.tasks.StructureUpgradeTask;
import travibot.engine.tasks.VillageUpdateTask;
import travibot.model.Structure;
import travibot.model.Village;
import travibot.resource.ResourceHelper;
import travibot.utility.RequestDelayGenarator;

/**
 * 
 * @author Petr Macek
 * 
 */
public class Session extends Observable implements Runnable {

    public enum State {
        CONNECTED, DISCONNECTED, STOPPED, LOGIN_FAILED, DISPOSED
    }

    public static final String MESSAGES_BUNDLE = "state";

    private static Logger log = Logger.getLogger(Session.class);

    private String connectionFactoryKey;

    private Session.State state;

    private UserProfile profile;

    private RequestDelayGenarator delayGenerator;

    private ParserFactory parserFactory;

    private ApplicationContext ctx;

    private Map<Integer, Village> villages = new HashMap<Integer, Village>();

    private TaskQueue taskQueue = new TaskQueue();

    Session(UserProfile profile, RequestDelayGenarator generator, ParserFactory factory, ApplicationContext ctx) {

        this.ctx = ctx;
        this.profile = profile;
        this.state = State.DISCONNECTED;
        this.connectionFactoryKey = profile.getUserName() + "@" + profile.getBaseURL();
        this.delayGenerator = generator;
        this.parserFactory = factory;
    }

    public ConnectionFactory getConnectionFactory() {

        return ConnectionFactory.getFactory(connectionFactoryKey);
    }

    public UserProfile getProfile() {

        return profile;
    }

    public Session.State getState() {

        return state;
    }

    public String getStateDescription() {

        String message = ResourceHelper.getKeyValue(MESSAGES_BUNDLE, state.toString());

        return MessageFormat.format(message, new Object[] { this.getProfile().getBaseURL() });
    }

    public synchronized void connect() {

        log.info("Conneting session");
        if (state == State.DISCONNECTED) {
            log.info("Session was disconnectd - now opening ConnectionFactory");
            ConnectionFactory factory = getConnectionFactory();
            factory.setUseCookies(true);
            factory.setBaseURL(profile.getBaseURL());

            // nacteni a parsnuti login stranky
            LoginPageParser loginParser = (LoginPageParser)parserFactory.getParser(Parser.ParserType.LOGIN_PAGE,
                                                                                   factory.getInputStream("login.php"));

            // vyzvednuti dat z parsnute stranky
            log.info("Parsing LoginForm");
            LoginForm loginForm = loginParser.getLoginForm();

            // nastaveni do requestu
            factory.addArgument(loginForm.getNameInput().getName(), profile.getUserName());
            factory.addArgument(loginForm.getPasswordInput().getName(), profile.getPassword());

            Iterator<Input> inputIterator = loginForm.getInputs().iterator();
            while (inputIterator.hasNext()) {
                Input input = inputIterator.next();
                factory.addArgument(input.getName(), input.getValue());
            }

            // poslat request
            factory.send("dorf3.php");
            VillagesParser villagesParser = (VillagesParser)parserFactory.getParser(Parser.ParserType.VILLAGES, factory
                    .getInputStream("dorf3.php"));

            List<Village> villagesList = villagesParser.getVillages();

            if (villagesList != null && villagesList.size() >= 1) {
                state = State.CONNECTED;
                for (Village v : villagesList) {
                    villages.put(v.getId(), v);
                    addVillageUpdateTask(v);
                }
            }
            else {
                state = State.LOGIN_FAILED;
            }
            setChanged();
            notifyObservers(state);

            // now the cookies should be initialized we can close the connection
            factory.close();
            factory.setRequestMethod("GET");

        }
    }

    private void updateVillages() throws InterruptedException {

        log.info("Updating village state");
        ConnectionFactory factory = getConnectionFactory();
        factory.setRequestMethod("GET");

        Set<Integer> keys = villages.keySet();
        for (Integer key : keys) {
            Village village = villages.get(key);            

            addVillageUpdateTask(village);
        }
        log.info("Closing ConnectionFactory after parsing");
        factory.close();
    }

    /**
     * Completely disconnects session so it will be no longer usable
     */
    public synchronized void disconnect() {

        state = State.DISCONNECTED;
        setChanged();
        notifyObservers(state);
    }

    /**
     * Sets session to non-automatic state, where all requests are manually invoked by 
     * the user. No automatic village updates are done.
     */
    public synchronized void stop() {

        state = State.STOPPED;
        setChanged();
        notifyObservers(state);
    }
    
    /**
     * Sets session to automatic state where all villages are observed periodically.
     */
    public synchronized void start() {

        state = State.CONNECTED;
        setChanged();
        notifyObservers(state);
    }
    
    public synchronized void dispose() {

        state = State.DISPOSED;
        setChanged();
        notifyObservers(state);
    }

    public synchronized Collection<Village> getVillages() {

        return villages.values();
    }

    public void run() {

        while (state != State.DISPOSED) {
            try {
                // if not in auto-mode all request should be invoked immediately
                if(state == State.CONNECTED)
                    Thread.sleep(delayGenerator.getMiliseconds());
                
                // if there is task to run, do it
                if ((state == State.CONNECTED || state == State.STOPPED) && !taskQueue.isEmpty()) {
                    if (Task.ExecutionResult.SUCCESS.equals(taskQueue.getCurrentTask().execute())) {
                        setChanged();
                        notifyObservers(state);
                    }
                }
                // else if there is no task the do regular update if in
                // CONNECTED state
                else if (state == State.CONNECTED)
                    updateVillages();
            }
            catch (InterruptedException e) {
                log.error("Interrupted Exception caught " + e);
            }
        }
    }

    public void addVillageUpdateTask(Village village) {

        VillageUpdateTask task = (VillageUpdateTask)ctx.getBean("villageUpdateTask");
        if (task != null) {
            task.setVillage(village);
            task.setConnectionFactory(getConnectionFactory());
            taskQueue.addTask(task);
        }
    }
    
    public void addStructureUpgradeTask(Village village, Structure structure){
        StructureUpgradeTask task = (StructureUpgradeTask)ctx.getBean("structureUpgradeTask");
        if(task != null){
            task.setVillage(village);
            task.setStructure(structure);
            task.setConnectionFactory(getConnectionFactory());
            //TODO: add calculation of scheduled time and query for resources
            taskQueue.addTask(task);
        }        
    }

    
    public TaskQueue getTaskQueue() {
    
        return taskQueue;
    }
    
    

}
